package com.lw.smartdevice.websocket.handler;

import com.lw.smartdevice.constans.DeviceStatus;
import com.lw.smartdevice.events.DevicePowerEvent;
import com.lw.smartdevice.events.DeviceStatusCheckEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @author Administrator
 * @version 1.0
 * @description:
 * @date 2022/3/24 22:15
 *
 * 对设备进行电源控制的websocket,主要是给继电器设备进行命令发送的
 */
public class BaseCommandHandler extends TextWebSocketHandler {

    private ApplicationContext context;

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }

    private ConcurrentMap<String,WebSocketSession> allClients=new ConcurrentHashMap<>();//保存了所有的连接的设备的map

    private ConcurrentMap<String,Long> lastHeartBeatMap=new ConcurrentHashMap<>();//记录每个设备最后一次心跳的时间

    protected ConcurrentMap<String, WebSocketSession> getAllClients() {
        return allClients;
    }

    protected ConcurrentMap<String, Long> getLastHeartBeatMap() {
        return lastHeartBeatMap;
    }

    /**
     * 连接建立
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        URI uri = session.getUri();
        String uriString = uri.toString();
        String deviceKey = uriString.substring(uriString.lastIndexOf("/") + 1);
        session.getAttributes().put("deviceKey",deviceKey);

        WebSocketSession socketSession = allClients.get(deviceKey);
        if(socketSession!=null){
            //之前有连接，先关闭
            socketSession.close();
        }
        allClients.put(deviceKey,session);//保存会话
        lastHeartBeatMap.put(deviceKey,System.currentTimeMillis());//记录最后的时间
        //设备连接之后，发布一个事件，更新设备状态
        //我们发送事件的目的是更新某个设备为在线状态,并且记录连接的ip和区域,我们需要这些数据, 其中设备的编号,ip只能在这边获取到,所以我们需要通过事件携带出去
        context.publishEvent(new DevicePowerEvent(deviceKey,session.getRemoteAddress().getHostString(), DeviceStatus.ONLINE));
    }

    /**
     * 收到消息
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.err.println("继电器收到消息"+message.getPayload());
        //记录当前设备最后一次发送数据的时间
        Object deviceKey = session.getAttributes().get("deviceKey");
        lastHeartBeatMap.put((String) deviceKey,System.currentTimeMillis());//记录最后的时间
    }

    /**
     * 连接关闭
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.err.println("有设备离线了");
        Object deviceKey = session.getAttributes().get("deviceKey");
        allClients.remove(deviceKey);
        //更新状态为离线
        context.publishEvent(new DevicePowerEvent((String) deviceKey,session.getRemoteAddress().getHostString(),DeviceStatus.OFFLINE));
    }


    @EventListener
    public void onEvent(DeviceStatusCheckEvent event){
        System.err.println("设备心跳检查开始了");
        allClients.entrySet().parallelStream().forEach(stringWebSocketSessionEntry -> {//entrySet()可以使用stream流遍历，可以开启异步的
            WebSocketSession socketSession = stringWebSocketSessionEntry.getValue();//遍历当前设备的会话
            String deviceId = stringWebSocketSessionEntry.getKey();//设备的标识
            Long lastHeartBeatTime = lastHeartBeatMap.get(deviceId);//获取到最后一次心跳
            if(System.currentTimeMillis()-lastHeartBeatTime>30000){
                //当前时间减去最后一次心跳时间大于30秒种，认为设备离线了
                //断线做三个事情；一个是关闭session，一个是从allClients移除设备，一个是从心跳map中移除设备
                if (socketSession.isOpen()) {
                    try {
                        socketSession.close();//关闭，触发上面的关闭监听会从上面的map中移除数据
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                lastHeartBeatMap.remove(deviceId);
            }
        });
    }


}
